﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QUT.Bio.Util {
	/// <summary>
	/// Selects "numberWanted" choices from a candidate pool containing "outOf" items 
	/// according to a uniform distribution.
	/// </summary>
	public class Selector {
		Random			generator;
		int             numberWanted;
		int             outOf;
		int				stillWanted;
		int				remaining;

		public Selector (
			Random generator,
			int numberWanted,
			int outOf
		) {
			this.generator = generator;
			this.numberWanted = numberWanted;
			this.outOf = outOf;
			stillWanted = numberWanted;
			remaining = outOf;
			Validate();
		}

		/// <summary>
		/// This property should be called in sequence until the number wanted 
		/// has been obtained, which is guaranteed to happen by the time the 
		/// the property has been evaluated "out of" times.
		/// <para>Side effects: outOf and numberWanted are updated as part of the decision process.</para>
		/// </summary>
		public bool SelectThis {
			get {
				Validate();

				if ( numberWanted == 0 || outOf == 0 ) return false;

				double probability = (double) numberWanted / outOf;
				double random      = generator.NextDouble();

				outOf--;

				if ( random <= probability ) {
					numberWanted--;
					return true;
				}
				else {
					return false;
				}
			}
		}

		private void Validate () {
			if ( stillWanted > remaining ) {
				throw new Exception( String.Format(
					"selector: numberWanted > outOf"
					+ "\n\tstillWanted = {0}"
					+ "\n\tremaining = {1}",
					stillWanted,
					remaining
				 ) );
			}

			if ( remaining < 0 ) {
				throw new Exception( string.Format(
					"selector: remaining <= 0"
					+ "\n\tremaining = {0}",
					remaining
				) );
			}
		}

		/// <summary>
		/// Gets the number of items that have yet to be chosen.
		/// </summary>
		public int StillWanted {
			get {
				return stillWanted;
			}
		}

		/// <summary>
		/// Gets the number of candidate items available for selection.
		/// </summary>
		public int Remaining {
			get {
				return remaining;
			}
		}

		/// <summary>
		/// Gets the original number wanted.
		/// </summary>
		public int NumberWanted {
			get {
				return numberWanted;
			}
		}

		/// <summary>
		/// Gets the original size of the pool of candidates.
		/// </summary>
		public int OutOf {
			get {
				return outOf;
			}
		}
		
		/// <summary>
		/// Restore the initial values of Remaining and StillWanted in 
		/// preparation for a new run of selections.
		/// </summary>
		public void Reset() {
			remaining = outOf;
			stillWanted = numberWanted;
		}
	}
}
